#pragma once

#include "../commons/constants.hpp"
#include <algorithm>
#include <functional>
#include <initializer_list>
#include <iostream>
#include <unordered_set>
#include <vector>
#include <exception>

using std::initializer_list;
using std::unordered_set;
using std::vector;

// functor to determine if a char is acceptable
class acceptableChars {
private:
    unordered_set<_OptChar> charSet;
    bool exist;

public:
    enum class MetaChar { DOT };
    bool isReverse() { return !exist; }
    void setExist(bool e) { 
        #ifdef __DEBUG
        __PRINT_LINE_HINT("setting exist");
        #endif
        exist = e;
        if(!e){
            charSet.clear();
            for (int i = 0; i < 128; ++i) {
                charSet.insert(i);
            }
        }
    }

public:
    acceptableChars(): exist(true){};
    acceptableChars(initializer_list<_OptChar> charList, bool existT = true)
        : charSet(charList), exist(existT){
        if(!exist){
            exist = true;
            auto ss = charSet;
            charSet.clear();
            for (int i = 0; i < 128; ++i) {
                if(!ss.count(i)){
                    charSet.insert(i);
                }
            }
        }
    }
    acceptableChars(unordered_set<_OptChar> charSet, bool exist = true)
        : charSet(charSet), exist(exist){
        if (!exist) {
            exist = true;
            auto ss = charSet;
            charSet.clear();
            for (int i = 0; i < 128; ++i) {
                if (!ss.count(i)) {
                    charSet.insert(i);
                }
            }
        }
    }
    acceptableChars(const acceptableChars &s) : charSet(s.charSet), exist(s.exist){};
    acceptableChars(MetaChar metachar){ // TODO support more metachar
        switch(metachar){
        case MetaChar::DOT: // TODO stupid implementation
            exist = true;
            for (int i = 0; i < 128; ++i) {
                charSet.insert(i);
            }
            break;
        default:
            throw std::runtime_error("No such metachar!");
        }
    }
    acceptableChars &operator=(const acceptableChars &s) {
        charSet = s.charSet;
        exist = s.exist;
        return *this;
    }
    bool match(_OptChar c) const { return !(exist ^ (bool)charSet.count(c)); }
    bool operator==(const acceptableChars &b) const {
        return charSet == b.charSet && exist == b.exist;
    }
    bool empty() const { return charSet.empty(); }
    int size() const { return charSet.size(); }
    void clear() { 
        charSet.clear();
        exist = true;
    }
    void insert(_OptChar ch) { 
        if(exist)
            charSet.insert(ch);
        else
            charSet.erase(ch);
    }
    void merge(const acceptableChars &s) {
        charSet.insert(s.charSet.begin(), s.charSet.end());
    }
    bool includes(const acceptableChars &s) const {
        for (auto i : s.charSet) {
            if (!((exist ^ s.exist) ^ (bool)charSet.count(i)))
                return false;
        }
        return true;
    }
    void setDifference(acceptableChars rhs) {
        charSet = getDifference(rhs).charSet;
    }
    acceptableChars getDifference(const acceptableChars &rhs) const { //TODO exist
        acceptableChars ret;
        if(exist && rhs.exist){
            for (auto i : charSet) {
                if (!(bool)rhs.charSet.count(i)) {
                    ret.charSet.insert(i);
                }
            }
        } else if(!exist && !rhs.exist){
            ret.exist = true;
            for (auto i : rhs.charSet) {
                if (!(bool)charSet.count(i)) {
                    ret.charSet.insert(i);
                }
            }
        } else if(exist && !rhs.exist){
            for (auto i : charSet) {
                if ((bool)rhs.charSet.count(i)) {
                    ret.charSet.insert(i);
                }
            }
        } else {
            for(auto i: charSet){
                ret.charSet.insert(i);
            }
            for(auto i: rhs.charSet){
                ret.charSet.insert(i);
            }
        }
        return ret;
    }
    void setIntersection(const acceptableChars &rhs) {
        charSet = getIntersection(rhs).charSet;
    }
    acceptableChars getIntersection(const acceptableChars &rhs) const { // TODO exist
        acceptableChars ret;
        if(exist && rhs.exist){
            for (auto i : charSet) {
                if ((bool)rhs.charSet.count(i)) {
                    ret.charSet.insert(i);
                }
            }
        } else if(!exist && !rhs.exist){
            for (auto i : charSet) {
                ret.charSet.insert(i);
            }
            for (auto i : rhs.charSet) {
                ret.charSet.insert(i);
            }
        } else if(exist && !rhs.exist){
            for(auto i: charSet){
                if(!(bool)rhs.charSet.count(i)){
                    ret.charSet.insert(i);
                }
            }
        } else {
            ret = rhs.getIntersection(*this);
        }
        return ret;
    }

public:
    auto begin() const noexcept { return charSet.begin(); }
    auto begin() noexcept { return charSet.begin(); }
    auto end() const noexcept { return charSet.end(); }
    auto end() noexcept { return charSet.end(); }
};
